/** * $RCSfile: ,v $ * $Revision: $ * $Date: $ * * Copyright (C) 2004-2011 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.spark.component.tabbedPane; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.Point; import java.awt.Rectangle; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.dnd.DragGestureEvent; import java.awt.dnd.DragGestureListener; import java.awt.dnd.DragSource; import java.awt.dnd.DragSourceDragEvent; import java.awt.dnd.DragSourceDropEvent; import java.awt.dnd.DragSourceEvent; import java.awt.dnd.DragSourceListener; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.jivesoftware.Spark; import org.jivesoftware.resource.SparkRes; import org.jivesoftware.spark.SparkManager; import org.jivesoftware.spark.ui.ChatRoomNotFoundException; import org.jivesoftware.spark.ui.ContactList; import org.jivesoftware.spark.util.ModelUtil; import org.jivesoftware.spark.util.SwingWorker; import org.jivesoftware.spark.util.log.Log; public class SparkTabbedPane extends JPanel { private static final long serialVersionUID = -9007068462231539973L; private static final String NAME = "SparkTabbedPane"; private List<SparkTabbedPaneListener> listeners = new ArrayList<SparkTabbedPaneListener>(); private JTabbedPane pane = null; private Icon closeInactiveButtonIcon; private Icon closeActiveButtonIcon; private boolean closeEnabled = false; private int dragTabIndex = -1; /** * The default Hand cursor. */ public static final Cursor HAND_CURSOR = new Cursor(Cursor.HAND_CURSOR); /** * The default Text Cursor. */ public static final Cursor DEFAULT_CURSOR = new Cursor( Cursor.DEFAULT_CURSOR); public SparkTabbedPane() { this(JTabbedPane.TOP); } public SparkTabbedPane(final Integer type) { this(type.intValue()); } public SparkTabbedPane(final int type) { pane = buildTabbedPane(type); pane.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); setLayout(new BorderLayout()); add(pane); ChangeListener changeListener = new ChangeListener() { public void stateChanged(ChangeEvent changeEvent) { JTabbedPane sourceTabbedPane = (JTabbedPane) changeEvent.getSource(); int index = sourceTabbedPane.getSelectedIndex(); if (index >= 0) { fireTabSelected(getTabAt(index), getTabAt(index).getComponent(), index); } } }; pane.addChangeListener(changeListener); closeInactiveButtonIcon = SparkRes.getImageIcon(SparkRes.CLOSE_WHITE_X_IMAGE); closeActiveButtonIcon = SparkRes.getImageIcon(SparkRes.CLOSE_DARK_X_IMAGE); } public SparkTab getTabContainingComponent(Component component) { for (Component comp : pane.getComponents()) { if (comp instanceof SparkTab) { SparkTab tab = (SparkTab) comp; if (tab.getComponent() == component) return tab; } } return null; } public SparkTab addTab(String title, Icon icon, final Component component) { return addTab(title, icon, component, null); } public SparkTab addTab(String title, Icon icon, final Component component, String tip) { final SparkTab sparktab = new SparkTab(this, component); TabPanel tabpanel = new TabPanel(sparktab, title, icon); pane.addTab(null, null, sparktab, tip); pane.setTabComponentAt(pane.getTabCount() - 1, tabpanel); fireTabAdded(sparktab, component, getTabPosition(sparktab)); return sparktab; } public SparkTab getTabAt(int index) { return ((SparkTab) pane.getComponentAt(index)); } public int getTabPosition(SparkTab tab) { return pane.indexOfComponent(tab); } public Component getComponentInTab(SparkTab tab) { return tab.getComponent(); } public void setIconAt(int index, Icon icon) { Component com = pane.getTabComponentAt(index); if (com instanceof TabPanel) { TabPanel panel = (TabPanel) com; panel.setIcon(icon); } } public void setTitleAt(int index, String title) { if (index > 0) { Component com = pane.getTabComponentAt(index); if (com instanceof TabPanel) { TabPanel panel = (TabPanel) com; panel.setTitle(title); } } } public void setTitleColorAt(int index, Color color) { Component com = pane.getTabComponentAt(index); if (com instanceof TabPanel) { TabPanel panel = (TabPanel) com; panel.setTitleColor(color); } } /* * Updates the tab colors for unread,active and inactive tabs */ public void updateActiveTab() { for (int i = 0; i < pane.getTabCount(); ++i) { Component com = pane.getTabComponentAt(i); TabPanel panel = (TabPanel) com; Font oldFont = panel.getFont(); try { if (SparkManager.getChatManager().getChatContainer().getChatRoom(i).getUnreadMessageCount() == 0) { if (i == getSelectedIndex()) { panel.setTitleFont(new Font(oldFont.getFontName(), Font.BOLD, oldFont.getSize())); panel.setTitleColor((Color) UIManager.get("Chat.activeTabColor")); } else { panel.setTitleFont(new Font(oldFont.getFontName(), Font.PLAIN, oldFont.getSize())); panel.setTitleColor((Color) UIManager.get("Chat.inactiveTabColor")); } } } catch (ChatRoomNotFoundException e) { //Do nothing } } } public void setTitleBoldAt(int index, boolean bold) { Component com = pane.getTabComponentAt(index); if (com instanceof TabPanel) { TabPanel panel = (TabPanel) com; panel.setTitleBold(bold); } } public void setTitleFontAt(int index, Font font) { Component com = pane.getTabComponentAt(index); if (com instanceof TabPanel) { TabPanel panel = (TabPanel) com; panel.setTitleFont(font); } } public Font getDefaultFontAt(int index) { Component com = pane.getTabComponentAt(index); if (com instanceof TabPanel) { TabPanel panel = (TabPanel) com; return panel.getDefaultFont(); } return null; } public String getTitleAt(int index) { return pane.getTitleAt(index); } public int getTabCount() { return pane.getTabCount(); } public void setSelectedIndex(int index) { pane.setSelectedIndex(index); } public int indexOfComponent(Component component) { for (Component comp : pane.getComponents()) { if (comp instanceof SparkTab) { SparkTab tab = (SparkTab) comp; if (tab.getComponent() == component) return pane.indexOfComponent(tab); } } return -1; } public Component getComponentAt(int index) { return ((SparkTab) pane.getComponentAt(index)).getComponent(); } public Component getTabComponentAt(int index) { return pane.getTabComponentAt(index); } public Component getTabComponentAt(SparkTab tab) { return pane.getTabComponentAt(indexOfComponent(tab)); } public Component getSelectedComponent() { if (pane.getSelectedComponent() instanceof SparkTab) { SparkTab tab = (SparkTab) pane.getSelectedComponent(); return tab.getComponent(); } return null; } public void removeTabAt(int index) { pane.remove(index); } public int getSelectedIndex() { return pane.getSelectedIndex(); } public void setCloseButtonEnabled(boolean enable) { closeEnabled = enable; } public void addSparkTabbedPaneListener(SparkTabbedPaneListener listener) { listeners.add(listener); } public void removeSparkTabbedPaneListener(SparkTabbedPaneListener listener) { listeners.remove(listener); } protected void fireTabAdded(SparkTab tab, Component component, int index) { final Iterator<SparkTabbedPaneListener> list = ModelUtil.reverseListIterator(listeners .listIterator()); while (list.hasNext()) { (list.next()).tabAdded(tab, component, index); } } public JPanel getMainPanel() { return this; } public void removeComponent(Component comp) { int index = indexOfComponent(comp); if (index != -1) { removeTabAt(index); } } protected void fireTabRemoved(SparkTab tab, Component component, int index) { final Iterator<SparkTabbedPaneListener> list = ModelUtil.reverseListIterator(listeners .listIterator()); while (list.hasNext()) { (list.next()).tabRemoved(tab, component, index); } } protected void fireTabSelected(SparkTab tab, Component component, int index) { final Iterator<SparkTabbedPaneListener> list = ModelUtil.reverseListIterator(listeners .listIterator()); while (list.hasNext()) { (list.next()).tabSelected(tab, component, index); } } protected void allTabsClosed() { final Iterator<SparkTabbedPaneListener> list = ModelUtil.reverseListIterator(listeners .listIterator()); while (list.hasNext()) { list.next().allTabsRemoved(); } } public void close(SparkTab sparktab) { int closeTabNumber = pane.indexOfComponent(sparktab); pane.removeTabAt(closeTabNumber); fireTabRemoved(sparktab, sparktab.getComponent(), closeTabNumber); if (pane.getTabCount() == 0) { allTabsClosed(); } } private class TabPanel extends JPanel { private static final long serialVersionUID = -8249981130816404360L; private final BorderLayout layout = new BorderLayout(5, 5); private final Font defaultFontPlain = new Font("Dialog", Font.PLAIN, 11); private final Font defaultFontBold = new Font("Dialog", Font.BOLD, 11); private JLabel iconLabel; private JLabel titleLabel; private JLabel tabCloseButton = new JLabel(closeInactiveButtonIcon); public TabPanel(final SparkTab sparktab, String title, Icon icon) { setOpaque(false); this.setLayout(layout); titleLabel = new JLabel(title); titleLabel.setFont(closeEnabled ? defaultFontBold : defaultFontPlain); if (icon != null) { iconLabel = new JLabel(icon); add(iconLabel, BorderLayout.WEST); } add(titleLabel, BorderLayout.CENTER); if (closeEnabled) { tabCloseButton.addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent mouseEvent) { if (Spark.isWindows()) { tabCloseButton.setIcon(closeActiveButtonIcon); } setCursor(HAND_CURSOR); } public void mouseExited(MouseEvent mouseEvent) { if (Spark.isWindows()) { tabCloseButton.setIcon(closeInactiveButtonIcon); } setCursor(DEFAULT_CURSOR); } public void mousePressed(MouseEvent mouseEvent) { final SwingWorker closeTimerThread = new SwingWorker() { public Object construct() { try { Thread.sleep(100); } catch (InterruptedException e) { Log.error(e); } return true; } public void finished() { close(sparktab); } }; closeTimerThread.start(); } }); add(tabCloseButton, BorderLayout.EAST); } } @Override public Dimension getPreferredSize() { Dimension dim = super.getPreferredSize(); if (closeEnabled && titleLabel.getText() != null && titleLabel.getText().length() < 6 && dim.getWidth() < 80) { return new Dimension(80, dim.height); } else { return dim; } } public Font getDefaultFont() { return defaultFontPlain; } public void setIcon(Icon icon) { iconLabel.setIcon(icon); } public void setTitle(String title) { titleLabel.setText(title); } public void setTitleColor(Color color) { titleLabel.setForeground(color); titleLabel.validate(); titleLabel.repaint(); } public void setTitleBold(boolean bold) { Font oldFont = titleLabel.getFont(); Font newFont; if (bold) { newFont = new Font(oldFont.getFontName(), Font.BOLD, oldFont.getSize()); } else { newFont = new Font(oldFont.getFontName(), Font.PLAIN, oldFont.getSize()); } titleLabel.setFont(newFont); titleLabel.validate(); titleLabel.repaint(); titleLabel.revalidate(); } public void setTitleFont(Font font) { titleLabel.setFont(font); titleLabel.validate(); titleLabel.repaint(); titleLabel.revalidate(); } } /** * Drag and Drop */ public void enableDragAndDrop() { final DragSourceListener dsl = new DragSourceListener() { @Override public void dragDropEnd(DragSourceDropEvent event) { dragTabIndex = -1; } @Override public void dragEnter(DragSourceDragEvent event) { event.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop); } @Override public void dragExit(DragSourceEvent event) {} @Override public void dragOver(DragSourceDragEvent event) {} @Override public void dropActionChanged(DragSourceDragEvent event) {} }; final Transferable t = new Transferable() { private final DataFlavor FLAVOR = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType, NAME); @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { return pane; } @Override public DataFlavor[] getTransferDataFlavors() { DataFlavor[] f = new DataFlavor[1]; f[0] = this.FLAVOR; return f; } @Override public boolean isDataFlavorSupported(DataFlavor flavor) { return flavor.getHumanPresentableName().equals(NAME); } }; final DragGestureListener dgl = new DragGestureListener() { @Override public void dragGestureRecognized(DragGestureEvent event) { dragTabIndex = pane.indexAtLocation(event.getDragOrigin().x, event.getDragOrigin().y); try { event.startDrag(DragSource.DefaultMoveDrop, t, dsl); } catch(Exception idoe) { Log.error(idoe); } } }; final DropTargetListener dtl = new DropTargetListener() { @Override public void dragEnter(DropTargetDragEvent event) {} @Override public void dragExit(DropTargetEvent event) {} @Override public void dragOver(DropTargetDragEvent event) {} @Override public void drop(DropTargetDropEvent event) { int dropTabIndex = getTargetTabIndex(event.getLocation()); moveTab(dragTabIndex,dropTabIndex); } @Override public void dropActionChanged(DropTargetDragEvent event) {} }; new DropTarget(pane, DnDConstants.ACTION_COPY_OR_MOVE, dtl, true); new DragSource().createDefaultDragGestureRecognizer(pane, DnDConstants.ACTION_COPY_OR_MOVE, dgl); } private void moveTab(int prev, int next) { if (next < 0 || prev == next) { return; } Component cmp = pane.getComponentAt(prev); Component tab = pane.getTabComponentAt(prev); String str = pane.getTitleAt(prev); Icon icon = pane.getIconAt(prev); String tip = pane.getToolTipTextAt(prev); boolean flg = pane.isEnabledAt(prev); int tgtindex = prev > next ? next : next - 1; pane.remove(prev); pane.insertTab(str, icon, cmp, tip, tgtindex); pane.setEnabledAt(tgtindex, flg); if (flg) pane.setSelectedIndex(tgtindex); pane.setTabComponentAt(tgtindex, tab); } private int getTargetTabIndex(Point point) { Point tabPt = SwingUtilities.convertPoint(pane, point, pane); boolean isTB = pane.getTabPlacement()==JTabbedPane.TOP || pane.getTabPlacement()==JTabbedPane.BOTTOM; for(int i=0;i < getTabCount();i++) { Rectangle r = pane.getBoundsAt(i); if(isTB) r.setRect(r.x-r.width/2, r.y, r.width, r.height); else r.setRect(r.x, r.y-r.height/2, r.width, r.height); if(r.contains(tabPt)) return i; } Rectangle r = pane.getBoundsAt(getTabCount()-1); if(isTB) r.setRect(r.x+r.width/2, r.y, r.width, r.height); else r.setRect(r.x, r.y+r.height/2, r.width, r.height); return r.contains(tabPt)?getTabCount():-1; } protected JTabbedPane buildTabbedPane(final int type) { return new JTabbedPane(type); } protected JTabbedPane getTabbedPane() { return pane; } }